﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Windows.Forms;
using FiddlerAmfParser.Controls;
using FiddlerAmfParser.OutputProcessing;
using FiddlerAmfParser.Utils.Logging;
using FluorineFx;
using FluorineFx.IO;
using log4net;
using log4net.Core;
using log4net.Layout;

namespace FiddlerAmfParser.Rendering
{
	public class AmfRenderer
	{
		private readonly List<IOutputProcessorPlugin> _outputProcessors;

		private readonly ILog _logger = LogManager.GetLogger(typeof (AmfRenderer));

		public AmfRenderer(IEnumerable<IOutputProcessorPlugin> outputProcessors)
		{
			_outputProcessors = outputProcessors.OrderBy(processor => processor.RenderOrder).ToList();
		}

		public void RenderAmfToTree(byte[] body, AmfOutputControl outputControl)
		{
			AMFMessage message = null;
			ConfigureLogging(outputControl);

			var decodedBody = body;
			var stream = new MemoryStream(decodedBody);
				
			try
			{
				var deserializer = new AMFDeserializer(stream);
				message = deserializer.ReadAMFMessage();
			}
			catch (DecoderFallbackException ex)
			{
				_logger.Warn("Encoding exception occured, possible AMF3 object definition. Falling back to EXPERIMENTAL AMF3 single-object decoding!");

				stream.Position = 0;

				var reader = new AMFReader(stream);
				reader.FaultTolerancy = true;

				var data = reader.ReadAMF3Data();

				message = new AMFMessage(3);
				message.AddBody(new AMFBody(string.Empty, string.Empty, data));
			}

			var parserContext = new ParserContext {Output = outputControl, SourceMessage = message};

			ExecuteProcessors(processor => processor.ProcessAmfMessage(parserContext, message));

			foreach (var msgBody in message.Bodies)
			{
				var msgBodyInternal = msgBody;
				ExecuteProcessors(processor => processor.ProcessAmfBody(parserContext, msgBodyInternal));
			}
		}

		private void ConfigureLogging(AmfOutputControl outputControl)
		{
			LogManager.ResetConfiguration();

			var appender = new DelegateAppender
			{
				LogTextMethod = text => outputControl.StatusBox.AppendText(text),
				Layout = new SimpleLayout(),
				Threshold = Level.Warn
			};

			log4net.Config.BasicConfigurator.Configure(appender);
		}

		private void ExecuteProcessors(Action<IOutputProcessorPlugin> processAction)
		{
			foreach (var processor in _outputProcessors)
			{
				processAction(processor);
			}
		}

		public static void RenderObject(ASObject content, TreeNode rootNode)
		{
			foreach (var contentNode in content)
			{
				var node = rootNode.Nodes.Add(contentNode.Key);
				RenderValue(contentNode.Value, node);
			}
		}

		public static void RenderValue(object value, TreeNode node)
		{
			if (value == null)
			{
				node.Nodes.Add(new TreeNode("null"));
				return;
			}

			if (value is ASObject)
			{
				RenderObject((ASObject)value, node);
			}
			else if (value.GetType().IsArray)
			{
				int currentIndex = 0;
				foreach (var subContentNode in (object[])value)
				{
					var arrayNode = node.Nodes.Add(currentIndex.ToString(CultureInfo.InvariantCulture));
					RenderValue(subContentNode, arrayNode);
					currentIndex++;
				}
			}
			else
			{
				node.Nodes.Add(new TreeNode(value.ToString()));
			}
		}
	}
}